import Mota from "@/mota";
import WatchNumber from "@/utils/WatchNumber";
import { DrawSize, ImageVisibility, UnitInfo } from "@/mota/data/MapDataUtils";
import { FileSize, GetImage, GetImageElement, ImageName } from "@/mota/data/ImageLoader";
import { FontWeight } from "./TipUtils";

export interface DrawTool<CHILDREN extends DrawTool<CHILDREN>> {
	/**
	 * 1.左栏
	 * 2.单位
	 * 3.英雄
	 * 4.英雄交互(背包,物品,商店,对话)
	 * 5.创建单位
	 * 6.创建层
	 * 7.提示
	 * 8.边框
	 * 9.创建层边框
	 */
	ZIndex: number;
	Draw?: (draw: DrawUtils) => void;
	Children?: CHILDREN[] | undefined;
}

export type DrawImageParam = [number, number] | [number, number, number, number] | [number, number, number, number, number, number, number, number];

export default class DrawUtils {

	public readonly Mota: Mota;

	/** 画板元素 */
	public readonly canvas: HTMLCanvasElement;

	public readonly _2d: CanvasRenderingContext2D;

	public padding = new WatchNumber(20);

	public x: number = 0;

	public y: number = 0;

	public size = new WatchNumber(10);

	public constructor(mota: Mota, el: HTMLCanvasElement) {
		this.Mota = mota;
		this.canvas = el;
		this._2d = el.getContext("2d") as CanvasRenderingContext2D;
		this.AutoResize();
		const resize = new ResizeObserver(() => { this.AutoResize(); });
		resize.observe(el);
		mota.eventRemove.Add(() => resize.unobserve(el));
	}

	/** 界面自适应 */
	public AutoResize(): void {
		const { padding: { Value: padding }, canvas, _2d } = this;
		const width = canvas.width = canvas.offsetWidth;
		const height = canvas.height = canvas.offsetHeight;
		_2d.imageSmoothingEnabled = false;
		const Padding = padding * 2;
		const mostWidth = (width - Padding) / 15 * 11;
		let size = (Math.min(mostWidth, height - Padding) / 11);
		this.size.Value = size;
		this.x = Math.floor((width - (size * 15)) / 2);
		this.y = Math.floor(((height - (size * 11)) / 2));
	}

	public GetDrawListFor(list: Set<DrawTool<any>>, unit: DrawTool<any> | undefined) {
		if (unit !== undefined) {
			list.add(unit);
			if (unit.Children !== undefined) {
				for (const child of unit.Children) {
					this.GetDrawListFor(list, child);
				}
			}
		}
	}

	public GetDrawList() {
		const { Mota } = this;
		const list = new Set<DrawTool<any>>();
		this.GetDrawListFor(list, Mota);
		return Array.from(list);
	}

	public DrawAll(): void {
		const { Mota, _2d, x, y, size: { Value: size } } = this;

		this.DrawClear();

		_2d.save();

		_2d.translate(x, y);

		for (const info of Mota.GetChildren().sort((x, y) => x.ZIndex - y.ZIndex)) {
			if (info.Draw) {
				info.Draw(this);
			}
		}

		_2d.restore();
	}

	public DrawClear(): void {
		const { canvas, _2d } = this;
		_2d.save();

		_2d.fillStyle = "rgb(30,30,30)";
		_2d.fillRect(0, 0, canvas.width, canvas.height);

		_2d.restore();
	}

	public DrawImage(name: ImageName, show: ImageVisibility, pos: number, fileSize: FileSize): void {
		const { _2d } = this;
		const imageInfo = GetImage(name);
		const [[fileName, , [x, y, w, h]], posList] = imageInfo;
		const length = posList.length;
		if (pos >= length) {
			pos = length - 1;
		}
		const [col, row] = posList[pos];
		const image = GetImageElement(fileName);
		_2d.save();
		if (typeof show === "number") {
			_2d.globalAlpha = show;
		}
		this.drawImage(image, x + w * col, y + h * row, w, h, ...fileSize);
		_2d.restore();
	}

	public DrawUnti(unit: UnitInfo, fileCall?: (value: FileSize) => FileSize): void {
		const { _2d, Mota: { speedUtils, onBuild }, } = this;
		const { progress } = speedUtils;
		const [image, show, size, state, event] = unit;
		if (!onBuild && show === false) { return; }
		_2d.save();
		if (onBuild && (!show || event && event.has("remove"))) {
			_2d.globalAlpha = 0.5;
		}
		const pos = state === "first" ? 0 : typeof state === "number" ? state : progress.Value;
		this.ForSize(size, (x, y) => {
			const drawSize: FileSize = fileCall === undefined ? [x, y, 1, 1] : fileCall([x, y, 1, 1]);
			this.DrawImage(image, show, pos, drawSize);
		})
		_2d.restore();
	}

	public ForSize(size: DrawSize, backcall: (x: number, y: number) => void): void {
		if (size.length === 2) {
			backcall(...size);
		} else {
			const [x1, y1, x2, y2] = size;
			for (let x = x1; x <= x2; x++) {
				for (let y = y1; y <= y2; y++) {
					backcall(x, y);
				}
			}
		}
	}

	public MoveSize(x: number, y: number, size: DrawSize): DrawSize {
		if (size.length === 2) {
			const [tx, ty] = size;
			return [tx + x, ty + y];
		} else {
			const [x1, y1, x2, y2] = size;
			return [x1 + x, y1 + y, x2 + x, y2 + y];
		}
	}

	public DrawBackground(background: [ImageName, DrawSize]): void {
		const [imageName, size] = background;
		this.ForSize(size, (x, y) => {
			this.DrawUnti([imageName, true, [x, y], "run"]);
		});
	}

	public DrawTextAutoLine(text: string, x: number, y: number, size: number, lineHeight: number, maxWidth: number) {
		const { _2d } = this;
		let px = 0;
		let py = 0;
		Array.from(text).forEach(char => {
			const width = this.measureText(char);
			if (px + width > maxWidth || char === "\n") {
				py += size * lineHeight;
				px = 0;
			}
			this.fillText(char, x + px, y + py);
			px += width;
		});
	}

	public SetFont(weight: FontWeight, fontSize: number, fontFamily: string) {
		const { _2d, size: { Value: size } } = this;
		_2d.font = `${weight} ${fontSize * size}px ${fontFamily}`;
	}

	public SetLineWidth(lineWidth: number) {
		const { _2d, size: { Value: size } } = this;
		_2d.lineWidth = lineWidth * size;
	}



	public translate(x: number, y: number): void {
		const { _2d, size: { Value: size } } = this;
		_2d.translate(x * size, y * size);
	}

	public fillText(text: string, x: number, y: number, maxWidth?: number | undefined): void {
		const { _2d, size: { Value: size } } = this;
		_2d.fillText(text, x * size, y * size, maxWidth);
	}

	public moveTo(x: number, y: number): void {
		const { _2d, size: { Value: size } } = this;
		_2d.moveTo(x * size, y * size);
	}

	public lineTo(x: number, y: number): void {
		const { _2d, size: { Value: size } } = this;
		_2d.lineTo(x * size, y * size);
	}

	public drawImage(image: CanvasImageSource, dx: number, dy: number): void;
	public drawImage(image: CanvasImageSource, dx: number, dy: number, dw: number, dh: number): void;
	public drawImage(image: CanvasImageSource, sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number): void;
	public drawImage(image: CanvasImageSource, ...param: DrawImageParam): void {
		const { _2d, size: { Value: size } } = this;
		if (param.length === 2) {
			_2d.drawImage(image, param[0] * size, param[1] * size);
		} else if (param.length === 4) {
			_2d.drawImage(image, param[0] * size, param[1] * size, param[2] * size, param[3] * size);
		} else if (param.length === 8) {
			_2d.drawImage(image, param[0], param[1], param[2], param[3], param[4] * size, param[5] * size, param[6] * size, param[7] * size);
		}
	}

	public fillRect(x: number, y: number, w: number, h: number): void {
		const { _2d, size: { Value: size } } = this;
		_2d.fillRect(x * size, y * size, w * size, h * size);
	}

	public strokeRect(x: number, y: number, w: number, h: number): void {
		const { _2d, size: { Value: size } } = this;
		_2d.strokeRect(x * size, y * size, w * size, h * size);
	}

	public measureText(text: string): number {
		const { _2d, size: { Value: size } } = this;
		return _2d.measureText(text).width / size;
	}
}
